#pragma once

#ifdef RAPIDJSON_HAS_STDSTRING
#undef RAPIDJSON_HAS_STDSTRING
#define RAPIDJSON_HAS_STDSTRING 1
#endif
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <rapidjson/document.h>

#include "type_traits.hpp"

namespace bzbee
{

    namespace Json
    {

        BZBEE_HAS_MEMBER_FUNCTION(toJson);

        BZBEE_HAS_MEMBER_FUNCTION(fromJson);
        //////////////////////////////////////////////////////////////////////////
        inline bool fromJson(const rapidjson::Value& value, const char* szMemberName, bool& t)
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = false;
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                t = false;
                return false;
            }
            const auto& member = value[szMemberName];
            if (!member.IsBool())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid bool type" << DUMP_VAR(szMemberName));
#endif
                t = false;
                return false;
            }
            t = member.GetBool();
            return true;
        }

        inline bool fromJson(const rapidjson::Value& value, bool& t)
        {
            t = value.GetBool();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && std::is_signed< decay_t<T> >::value
            && (sizeof(T) <= sizeof(int32_t))
            , bool >::type
            fromJson(const rapidjson::Value& value, const char* szMemberName, T& t)
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = false;
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            const auto& member = value[szMemberName];
            if (!member.IsInt())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid int type" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            t = member.GetInt();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && std::is_signed< decay_t<T> >::value
            && (sizeof(T) <= sizeof(int32_t))
            , bool >::type
            fromJson(const rapidjson::Value& value, T& t)
        {
            t = value.GetInt();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && !std::is_signed< decay_t<T> >::value
            && (sizeof(T) <= sizeof(int32_t))
            , bool >::type
            fromJson(const rapidjson::Value& value, const char* szMemberName, T& t)
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = T();
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            const auto& member = value[szMemberName];
            if (!member.IsUint())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid uint type" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            t = member.GetUint();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && !std::is_signed< decay_t<T> >::value
            && (sizeof(T) <= sizeof(int32_t))
            , bool >::type
            fromJson(const rapidjson::Value& value, T& t)
        {
            t = value.GetUint();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && std::is_signed< decay_t<T> >::value
            && (sizeof(T) > sizeof(int32_t) && sizeof(T) <= sizeof(int64_t))
            , bool > ::type
            fromJson(const rapidjson::Value& value, const char* szMemberName, T& t)
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = T();
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            const auto& member = value[szMemberName];
            if (!member.IsInt64())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid int64 type" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            t = member.GetInt64();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && std::is_signed< decay_t<T> >::value
            && (sizeof(T) > sizeof(int32_t) && sizeof(T) <= sizeof(int64_t))
            , bool > ::type
            fromJson(const rapidjson::Value& value, T& t)
        {
            t = value.GetInt64();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && !std::is_signed< decay_t<T> >::value
            && (sizeof(T) > sizeof(int32_t) && sizeof(T) <= sizeof(int64_t))
            , bool > ::type
            fromJson(const rapidjson::Value& value, const char* szMemberName, T& t)
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = T();
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            const auto& member = value[szMemberName];
            if (!member.IsUint64())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid uint64 type" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            t = member.GetUint64();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && !std::is_signed< decay_t<T> >::value
            && (sizeof(T) > sizeof(int32_t) && sizeof(T) <= sizeof(int64_t))
            , bool > ::type
            fromJson(const rapidjson::Value& value, T& t)
        {
            t = value.GetUint64();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_floating_point< decay_t<T> >::value, bool >::type
            fromJson(const rapidjson::Value& value, const char* szMemberName, T& t)
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = T();
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            const auto& member = value[szMemberName];
            if (!member.IsDouble())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid double type" << DUMP_VAR(szMemberName));
#endif
                t = T();
                return false;
            }
            t = member.GetDouble();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_floating_point< decay_t<T> >::value, bool >::type
            fromJson(const rapidjson::Value& value, T& t)
        {
            t = value.GetDouble();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< isString< T >::value, bool >::type
            fromJson(const rapidjson::Value& value, const char* szMemberName, T& t)
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = T();
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                t.clear();
                return false;
            }
            const auto& member = value[szMemberName];
            if (!member.IsString())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid string type" << DUMP_VAR(szMemberName));
#endif
                t.clear();
                return false;
            }
            t = member.GetString();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< isString< T >::value, bool >::type
            fromJson(const rapidjson::Value& value, T& t)
        {
            t = value.GetString();
            return true;
        }

        template< typename T >
        inline typename std::enable_if< HasMemberFunction_fromJson< T, const rapidjson::Value& >::value, bool >::type
            fromJson(const rapidjson::Value& value, const char* szMemberName, T& t)
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = T();
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                return false;
            }
            const auto& object = value[szMemberName];
            if (!object.IsObject())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid object type" << DUMP_VAR(szMemberName));
#endif
                return false;
            }
            t.fromJson(value);
            return true;
        }

        template< typename T, size_t N >
        inline typename std::enable_if< HasMemberFunction_fromJson< T, const rapidjson::Value& >::value, bool >::type
            fromJson(const rapidjson::Value& value, const char* szMemberName, T(&t)[N])
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = T();
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                return false;
            }
            const auto& array = value[szMemberName];
            if (!array.IsArray())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid array type" << DUMP_VAR(szMemberName));
#endif
                return false;
            }
            auto size = array.Size();
            if (size != N)
            {
                // 放宽限制，数组元素数量不一致，以最小值为准
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_WARN("array size not match" << DUMP_VAR(szMemberName) << DUMP_VAR(size) << DUMP_VAR(N));
#endif
                size = size > N ? N : size;
            }
            bool result = true;
            for (rapidjson::SizeType i = 0; i < size; ++i)
            {
                result &= t[i].fromJson(array[i]);
            }
            return result;
        }

        template< typename T, size_t N >
        inline typename std::enable_if< !HasMemberFunction_fromJson< T, const rapidjson::Value& >::value, bool >::type
            fromJson(const rapidjson::Value& value, const char* szMemberName, T(&t)[N])
        {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
            CHECK_PTR_RETURN_FALSE(szMemberName);
#else
            if (nullptr == szMemberName)
            {
                t = T();
                return false;
            }
#endif
            if (!value.HasMember(szMemberName))
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("member not exist" << DUMP_VAR(szMemberName));
#endif
                return false;
            }
            const auto& array = value[szMemberName];
            if (!array.IsArray())
            {
#ifdef BOYEE_JSON_HELPER_ENABLE_LOG
                LOG_ERROR("invalid array type" << DUMP_VAR(szMemberName));
#endif
                return false;
            }
            auto size = array.Size();
            if (size != N)
            {
                // 放宽限制，数组元素数量不一致，以最小值为准
                LOG_WARN("array size not match" << DUMP_VAR(szMemberName) << DUMP_VAR(size) << DUMP_VAR(N));
                size = size > N ? N : size;
            }
            bool result = true;
            for (rapidjson::SizeType i = 0; i < size; ++i)
            {
                result &= fromJson(array[i], t[i]);
            }
            return result;
        }
        //////////////////////////////////////////////////////////////////////////
        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && std::is_signed< decay_t<T> >::value
            && (sizeof(T) <= sizeof(int32_t))
            , bool >::type
            toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, const T& t)
        {
            writer.Int(t);
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && !std::is_signed< decay_t<T> >::value
            && (sizeof(T) <= sizeof(int32_t))
            , bool >::type
            toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, const T& t)
        {
            writer.Uint(t);
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && std::is_signed< decay_t<T> >::value
            && (sizeof(T) > sizeof(int32_t) && sizeof(T) <= sizeof(int64_t))
            , bool > ::type
            toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, const T& t)
        {
            writer.Int64(t);
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_integral< decay_t<T> >::value
            && !std::is_signed< decay_t<T> >::value
            && (sizeof(T) > sizeof(int32_t) && sizeof(T) <= sizeof(int64_t))
            , bool > ::type
            toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, const T& t)
        {
            writer.Uint64(t);
            return true;
        }

        inline bool toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, bool t)
        {
            writer.Bool(t);
            return true;
        }

        template< typename T >
        inline typename std::enable_if< std::is_floating_point< decay_t<T> >::value, bool >::type
            toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, const T& t)
        {
            writer.Double(t);
            return true;
        }

        template< typename T >
        inline typename std::enable_if< isString< T >::value, bool >::type
            toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, const T& t)
        {
            writer.String(t);
            return true;
        }

        template< typename T >
        inline typename std::enable_if< HasMemberFunction_toJson< T, rapidjson::Writer< rapidjson::StringBuffer >& >::value, bool >::type
            toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, const T& t)
        {
            t.toJson(writer);
            return true;
        }

        template< typename T >
        inline bool toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, const char* szMemberName, const T& t)
        {
            CHECK_PTR_RETURN_FALSE(szMemberName);
            writer.String(szMemberName);
            toJson(writer, t);
            return true;
        }

        template< typename T, size_t N >
        inline bool toJson(rapidjson::Writer< rapidjson::StringBuffer >& writer, const char* szMemberName, const T(&t)[N])
        {
            CHECK_PTR_RETURN_FALSE(szMemberName);
            writer.String(szMemberName);
            writer.StartArray();
            for (const T& value : t)
            {
                toJson(writer, value);
            }
            writer.EndArray();
            return true;
        }

    } // !namespace JsonHelper
} // !namespace Boyee
